Skip to content

Extract SessionState: testable concurrency contract for Session#236

Merged
bernardladenthin merged 2 commits into
mainfrom
claude/focused-cray-mgzh1e
Jun 15, 2026
Merged

Extract SessionState: testable concurrency contract for Session#236
bernardladenthin merged 2 commits into
mainfrom
claude/focused-cray-mgzh1e

Conversation

@bernardladenthin

Copy link
Copy Markdown
Owner

Summary

  • Extract the lock-guarded streaming flag and two-phase transcript commit from Session into a new SessionState class, making the concurrency contract testable without the native model library.
  • Add model-free unit tests (SessionStateTest) pinning the streaming guard and transcript atomicity.
  • Add vmlens interleaving-analysis tests (SessionStateInterleavingTest, VmlensInterleavingSmokeTest) to verify strict user/assistant alternation under all thread interleavings.
  • Wire vmlens into the CI pipeline as a dedicated job running the vmlens Maven profile.

Rationale

Session previously owned both the concurrency logic (streaming guard, two-phase commit) and the model integration. This made the concurrency contract hard to test independently—any test required the native library and a model. SessionState extracts the pure-Java state machine, accepting the model call as a callback that runs under the lock. This enables:

  1. Unit testing (SessionStateTest): Verify the streaming guard and transcript atomicity with plain lambdas, no native code.
  2. Exhaustive interleaving analysis (SessionStateInterleavingTest): Use vmlens to explore every possible thread interleaving and confirm the invariant (strict user/assistant alternation, non-stuck streaming flag) holds under all orderings.
  3. Cleaner separation of concerns: Session now only orchestrates model calls; SessionState owns the concurrency contract.

Test plan

  • SessionStateTest (11 tests) covers send/stream/commit paths, error cases, and the streaming guard.
  • SessionStateInterleavingTest drives a send racing a stream through every interleaving under vmlens and asserts strict alternation.
  • VmlensInterleavingSmokeTest validates the vmlens setup with a minimal atomic-increment example.
  • Existing SessionConcurrencyTest and model-gated integration tests remain unchanged and pass.
  • CI is green: new vmlens job added to .github/workflows/publish.yml, default surefire run excludes vmlens tests (re-included only under the vmlens profile).

Related issues / PRs

Closes the concurrency-testing gap identified in the SessionState Javadoc: the compound atomicity of the streaming guard and two-phase commit is now pinned under vmlens.

Checklist

  • I have read CONTRIBUTING.md and CODE_OF_CONDUCT.md
  • My commits follow Conventional Commits
  • No security-sensitive changes

https://claude.ai/code/session_01HzCYFLjZZGFs4BsuUty35N

claude added 2 commits June 14, 2026 21:40
The vmlens profile + plugin + com.vmlens:api were already present, but no CI
job ever invoked them (only -P jcstress ran), so interleaving analysis ran
nowhere. Mirror streambuffer's dedicated vmlens job:

- New VmlensInterleavingSmokeTest: two threads increment a shared AtomicLong
  inside an AllInterleavings loop, asserting the sum is always 2.
- pom: move com.vmlens:api into the main test <dependencies> (transitive-dep-
  free, dependencyConvergence-safe) so the test compiles in every build; add a
  managed surefire <exclude> so the ordinary suite skips it (vacuous without the
  agent); narrow the vmlens profile to an <includes> of just the smoke test.
- publish.yml: add a lightweight pure-Java `vmlens` job (no native lib/models)
  running `mvn -Pvmlens test -Dtest=VmlensInterleavingSmokeTest
  -DfailIfNoTests=false` and uploading target/vmlens-report/.

Scope is staged to one class for now; widen the profile <includes> as more
concurrency tests are added (streambuffer runs vmlens over its whole suite).

https://claude.ai/code/session_01HzCYFLjZZGFs4BsuUty35N
Extract the lock-guarded streaming-flag + transcript state machine out of Session
into a new model-free SessionState (mirrors the testability-driven extraction of
ChatTranscript). The native model call is injected as a callback that SessionState
runs under its lock, between the streaming guard and the transcript commit, so
Session keeps exactly its previous serialization semantics, exception types, and
messages — behaviour-preserving refactor.

Why: SessionState is the repo's real vmlens target — a multi-variable
check-then-act (streamingActive + two-phase transcript commit), a different and
untested interleaving class from the single-volatile CancellationToken
Lincheck/jcstress coverage. Session itself isn't vmlens-testable (native calls);
the extracted helper is.

- New SessionState (public, root package) + Session delegates to it.
- New vmlens SessionStateInterleavingTest: a send round races a beginStream +
  commitStreamedReply; asserts strict user/assistant alternation and a non-stuck
  streaming flag under every interleaving.
- New model-free SessionStateTest (7 tests) pinning the extracted contract in the
  ordinary suite (the model-gated SessionConcurrencyTest can't run without a GGUF).
- pom: vmlens profile <includes> + managed surefire <excludes> widened to the
  **/vmlens/*.java package glob.
- publish.yml: vmlens job runs both vmlens tests via an explicit -Dtest list.

Verified: compile (Error Prone/NullAway/Checker), javadoc, spotbugs:check (0 bugs),
SessionStateTest, and both vmlens tests under the agent all green.

https://claude.ai/code/session_01HzCYFLjZZGFs4BsuUty35N
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
79.6% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@bernardladenthin bernardladenthin merged commit f543cb8 into main Jun 15, 2026
10 of 13 checks passed
@bernardladenthin bernardladenthin deleted the claude/focused-cray-mgzh1e branch June 15, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants